using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using SkaldRogueLike.Main.DungeonGenerating.DirectionProcessing;

namespace SkaldRogueLike.Main.DungeonGenerating.MapImplementations
{
    public abstract class Map : IEnumerable
    {
        #region Implementation of IEnumerable

        public IEnumerator GetEnumerator()
        {
            return DungeonCells.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        public int Width { get { return DungeonCells.GetLength(0); } }
        public int Height { get { return DungeonCells.GetLength(1); } }

        protected readonly DungeonCell[,] DungeonCells;
        public DungeonCell this[Point p]
        {
            get { return DungeonCells[p.X, p.Y]; }
            set { DungeonCells[p.X, p.Y] = value; }
        }
        public DungeonCell this[int x, int y]
        {
            get { return DungeonCells[x, y]; }
            set { DungeonCells[x, y] = value; }
        }

        protected Rectangle _bounds;
        public Rectangle Bounds { get { return _bounds; } }

        public IEnumerable<Point> CellLocations
        {
            get
            {
                for (var x = 0; x < Width; x++)
                    for (var y = 0; y < Height; y++)
                        yield return new Point(x, y);
            }
        }

        protected Map(int width, int height)
        {
            DungeonCells = new DungeonCell[width, height];
            _bounds = new Rectangle(0, 0, width, height);
        }

        protected bool IsOutOfMap(Point point)
        {
            return point.X < 0 || point.Y < 0 || point.X > Width - 1 || point.Y > Height - 1;
        }

        public bool HasAdjacentCell(Point point, Dir dir)
        {
            if (IsOutOfMap(point))
                return false;
            switch (dir)
            {
                case Dir.North:
                    return point.Y > 0;
                case Dir.South:
                    return point.Y < (Height - 1);
                case Dir.West:
                    return point.X > 0;
                case Dir.East:
                    return point.X < (Width - 1);
                default:
                    return false;
            }
        }

        protected Point GetAdjacentPoint(Point point, Dir dir)
        {
            if (!HasAdjacentCell(point, dir))
                throw new InvalidOperationException("No adjacent cell exists for the location and direction provided.");
            var resultPoint = new Point(point.X, point.Y);
            switch (dir)
            {
                case Dir.North:
                    resultPoint.Y--;
                    break;
                case Dir.South:
                    resultPoint.Y++;
                    break;
                case Dir.West:
                    resultPoint.X--;
                    break;
                case Dir.East:
                    resultPoint.X++;
                    break;
                default:
                    throw new ArgumentException("Invalid direction provided.");
            }
            return resultPoint;
        }
        
        protected abstract void InitializeCells();
    }
}